package server;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.concurrent.Semaphore;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
import javax.swing.JTextArea;

import crypto.GeneraChiavi;

public class GestoreClient extends Thread{
	private Socket client;
	private Database d;
	private ServerDatabase sd;
	private Semaphore mutex;
	private boolean login=false;
	private ObjectOutputStream output;
	private ObjectInputStream input;
	private int tipo=0;
	private String esegui;
	private final int INSERIMENTO=1;
	private final int SELECT=2;
	private final int REGISTRAZIONE=3;
	private final int VERIFICA_CODICE=4;
	private final int RECUPERO_PASSWORD=5;
	private final int RECUPERO_USERNAME=6;
	private final int UPDATE=7;
	private final int LOGIN=77;
	private final int SCAMBIA_CHIAVI=88;
	private final int CHIUDI_SOCKET=99;
	//tutti i codici delle richieste al server
	private boolean connesso=true;
	private JTextArea area;
	private ResultSet rs;
	private String username,password,email,telefono;
	private String codiceRandom;
	private int pid;
	private PublicKey chiavePubblica;
	private PrivateKey chiavePrivata;
	private Key chiaveDes;
	
	public void setServerDatabase(ServerDatabase s){
		this.sd=s;
	}
	
	public GestoreClient(Socket client, Database d, Semaphore mutex, JTextArea area, int pid) throws IOException {
		this.client=client;
		this.d=d;
		this.mutex=mutex;
		this.area=area;
		this.pid=pid;
		area.append("Gestore"+pid+": Ho accettato una connessione alle ore "+ora()+"\n");
		output = new ObjectOutputStream(client.getOutputStream());
		output.flush();
		input=new ObjectInputStream(client.getInputStream());
	}
	
	private String ora() {
		Calendar cal=new GregorianCalendar();
		int ora=cal.get(Calendar.HOUR_OF_DAY);
		int min=cal.get(Calendar.MINUTE);
		int sec=cal.get(Calendar.SECOND);
		return ora+":"+min+":"+sec;
	}

	public void run(){
		while(connesso){
			try {
				riceviTipo();
				if(tipo==SCAMBIA_CHIAVI){
					try {
						scambiaChiavi();
					} catch (NoSuchAlgorithmException e) {
						e.printStackTrace();
					} catch (InvalidKeySpecException e) {
						e.printStackTrace();
					} catch (InvalidKeyException e) {
						e.printStackTrace();
					} catch (NoSuchPaddingException e) {
						e.printStackTrace();
					} catch (ShortBufferException e) {
						e.printStackTrace();
					} catch (IllegalBlockSizeException e) {
						e.printStackTrace();
					} catch (BadPaddingException e) {
						e.printStackTrace();
					}
				}else if(tipo==LOGIN){
					effettuaLogin();
				}else if(tipo==INSERIMENTO && login){
					riceviRichiesta();
					eseguiInserimento();
				}else if(tipo==SELECT && login){
					riceviRichiesta();
					eseguiSelect();
					criptaEInviaResultSet();
				}else if(tipo==UPDATE && login){
					riceviRichiesta();
					eseguiUpdate();
				}else if(tipo==CHIUDI_SOCKET && login){
					criptaEInvia("Ciao!");
					client.close();
					connesso=false;
					login=false;
					sd.diminuisciIcon();
				}else if(tipo==REGISTRAZIONE){
					registrazione();
				}else if(tipo==VERIFICA_CODICE){
					verificaCodice();
				}else if(tipo==RECUPERO_PASSWORD){
					recuperaPassword();
				}else if(tipo==RECUPERO_USERNAME){
					recuperaUsername();
				}
			} catch (IOException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				area.append("Gestore"+pid+": INTERROTTO "+ora()+"\n");
				sd.diminuisciIcon();
			} catch (SQLException e) {
				e.printStackTrace();
			} catch (InvalidKeyException e) {
				e.printStackTrace();
			} catch (NoSuchAlgorithmException e) {
				e.printStackTrace();
			} catch (NoSuchPaddingException e) {
				e.printStackTrace();
			} catch (IllegalBlockSizeException e) {
				e.printStackTrace();
			} catch (BadPaddingException e) {
				e.printStackTrace();
			} catch (ShortBufferException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void effettuaLogin() throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, IOException, InterruptedException, SQLException {
		String username=new String(riceviEDecripta());
		String password=new String(riceviEDecripta());
		esegui="select pass from accesso.dati where username='"
				+ username + "'";
		eseguiSelect();
		if(rs==null) inviaOk(2);
		if (rs.next()){
			if (password.equals(rs.getString(1))) {
				inviaOk(1);
				this.username=username;
				area.append("Gestore"+pid+": LOGIN EFFETTUATO DA "+username+" "+ora()+" \n");
				login=true;
			} else{
				inviaOk(3);
			}
		}else{
			inviaOk(2);
		}
	}

	private void scambiaChiavi() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, ShortBufferException, IllegalBlockSizeException, BadPaddingException {
		GeneraChiavi gc=new GeneraChiavi();
		chiavePrivata=gc.getPrivate();
		chiavePubblica=gc.getPublic();
		output.write(gc.getPublic().getEncoded());
		output.flush();
		area.append("Ho inviato la chiave pubblica al client\n");
		Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
		Security.addProvider(cipher.getProvider());
		cipher.init(Cipher.DECRYPT_MODE, chiavePrivata);
		byte[] encryptFile = new byte[1000];
		int numByte = input.read(encryptFile);
		encryptFile = Arrays.copyOf( encryptFile , numByte );
		byte[] dencodeFilePrimaDecodifica = new byte[encryptFile.length];
		int ixplain_1 = 0;
		int ixcode_1 = 0;
		while ((encryptFile.length - ixcode_1) > 128) {
			ixplain_1 += cipher.doFinal(encryptFile, ixcode_1, 128, dencodeFilePrimaDecodifica, ixplain_1);
			ixcode_1 += 128;
		}
		ixplain_1 += cipher.doFinal(encryptFile, ixcode_1, encryptFile.length - ixcode_1, dencodeFilePrimaDecodifica,
				ixplain_1);
		byte[] tempDencodeFilePrimaDecodifica = new byte[ixplain_1];
		for (int i_1 = 0; i_1 < ixplain_1; i_1++) {
			tempDencodeFilePrimaDecodifica[i_1] = dencodeFilePrimaDecodifica[i_1];
		}
		dencodeFilePrimaDecodifica = tempDencodeFilePrimaDecodifica;
		chiaveDes=new SecretKeySpec(dencodeFilePrimaDecodifica,"DES");
	}

	private void eseguiUpdate() {
		try {
			mutex.acquire();
			d.getStat().executeUpdate(esegui);
			mutex.release();
			inviaOk(1);
		} catch (SQLException e) {
			inviaOk(e.getErrorCode());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public byte[] riceviEDecripta() throws IllegalBlockSizeException, BadPaddingException, IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InterruptedException{
		try{
			Cipher cifra = Cipher.getInstance("DES");
			cifra.init(Cipher.DECRYPT_MODE, chiaveDes);
			byte[] rec=new byte[20000];
			int num=input.read(rec);
			rec=Arrays.copyOf(rec,num);
			byte[] messaggio=cifra.doFinal(rec);
			return messaggio;
		}catch(NegativeArraySizeException e){
			this.interrupt();
			connesso=false;
			throw new InterruptedException();
		}
	}
	
	public void criptaEInvia(String s) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException{
		byte[] messaggio=s.getBytes();
		Cipher cifra = Cipher.getInstance("DES");
		cifra.init(Cipher.ENCRYPT_MODE, chiaveDes);
		output.write(cifra.doFinal(messaggio));
		output.flush();
	}
	
	private void recuperaUsername() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InterruptedException{
	try{
		byte[] messaggio=riceviEDecripta();
		String pas=new String(messaggio);
		byte[] messaggio1=riceviEDecripta();
		String em=new String(messaggio1);
		rs=d.getStat().executeQuery("Select pass from accesso.dati where email='"+em+"'");
		if(rs.next()){
			if(rs.getString(1).equals(pas)){
				criptaEInvia("Le abbiamo inviato una mail di recupero username. Faccia attenzione la prossima volta!");
				criptaEInvia("true");
				rs=d.getStat().executeQuery("select username from accesso.dati where email='"+em+"'");
				rs.next();
				String user=rs.getString(1);
				AutenticaRegistrazione ar = new AutenticaRegistrazione();
				ar.mandaMailThread(
						em,
						"Recupero Username",
						"Ecco a lei la sua username come da apposita richiesta.\n\nUSERNAME: "
								+ user
								+ "\n\nLa invitiamo a fare piu' attenzione.");
			}else{
				criptaEInvia("Combinazione Password-Email non corretta!");
				criptaEInvia("false");
			}
		}else{
			criptaEInvia("Account non esistente!");
			criptaEInvia("false");
		}
	}catch(EOFException e){
		area.append("Gestore"+pid+": ERRORE EOF con Socket "+client.getInetAddress()+" alle "+ora()+"\n");
		Thread.currentThread().interrupt();
	} catch (IOException e) {
		area.append("Gestore"+pid+": ERRORE IO con Socket "+client.getInetAddress()+" alle "+ora()+"\n");
		Thread.currentThread().interrupt();
	} catch (SQLException e) {
		area.append("Gestore"+pid+": ERRORE SQL con Socket "+client.getInetAddress()+" alle "+ora()+"\n");
		Thread.currentThread().interrupt();
	}
	}

	private void recuperaPassword() throws IOException, SQLException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, InterruptedException {
		byte[] m1=riceviEDecripta();
		String username=new String(m1);
		byte[] m2=riceviEDecripta();
		String email=new String(m2);
		rs=d.getStat().executeQuery("Select email from accesso.dati where username='"+username+"'");
		if(rs.next()){
			if(rs.getString(1).equals(email)){
				criptaEInvia("Le abbiamo inviato una mail di recupero password. Faccia attenzione la prossima volta!");
				criptaEInvia("true");
				rs=d.getStat().executeQuery("select pass from accesso.dati where username='"+username+"'");
				rs.next();
				String pass=rs.getString(1);
				AutenticaRegistrazione ar = new AutenticaRegistrazione();
				ar.mandaMailThread(
						email,
						"Recupero Password",
						"Ecco a lei la sua password come da apposita richiesta.\n\nPASSWORD: "
								+ pass
								+ "\n\nLa invitiamo a fare piu' attenzione.");
			}else{
				criptaEInvia("Combinazione Username-Email non corretta!");
				criptaEInvia("false");
			}
		}else{
			criptaEInvia("Account non esistente!");
			criptaEInvia("false");
		}
	}

	private void verificaCodice() throws IOException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, InterruptedException{
		String codiceTemp = "";
		try {
			byte[] m1=riceviEDecripta();
			codiceTemp = new String(m1);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		if(codiceTemp.equals(codiceRandom)){
			try {
				d.getStat().execute("insert into accesso.dati (username,pass,email,telefono,installato) values('"+username+"','"+password+"','"
					+email+"','"+telefono+"',0)");
				criptaEInvia("Registrazione effettuata con successo! A breve ti sara' mandata una mail con le sue credenziali.");
				criptaEInvia("true");
				AutenticaRegistrazione ar = new AutenticaRegistrazione();
				ar.mandaMailThread(
						email,
						"Registrazione effettuata!",
						"Salve,\n\n"
								+ "la registrazione e' stata effettuata con successo!\n "
								+ "In questa mail come promemoria le ricordiamo le sue credenziali.\n\n"
								+ "USERNAME: "
								+ username
								+ "\n"
								+ "PASSWORD: "
								+ password
								+ "\n\n\n"
								+ "Si prega di non rispondere al seguente messaggio poiche' auto-generato dal server.");
				area.append("Gestore"+pid+": NUOVO UTENTE CREATO "+username+" "+ora()+"\n");
			} catch (SQLException e) {
				try {
					criptaEInvia(e.getMessage());
					criptaEInvia("false");
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}else{
			criptaEInvia("Codice non corretto!");
			criptaEInvia("false");
		}
		
	}

	private void registrazione() throws IOException, SQLException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, InterruptedException {
		byte[] m1=riceviEDecripta();
		username=new String(m1);
		rs=d.getStat().executeQuery("Select * from accesso.dati where username='"+username+"'");
		if(!rs.next()){
			output.writeInt(1);
			output.flush();
			byte[] m2=riceviEDecripta();
			email=new String(m2);
			rs=d.getStat().executeQuery("Select * from accesso.dati where email='"+email+"'");
			if(!rs.next()){
				criptaEInvia("NO");
				byte[] m3=riceviEDecripta();
				password=new String(m3);
				byte[] m4=riceviEDecripta();
				telefono=new String(m4);
				criptaEInvia("Le abbiamo inviato una mail sulla sua casella di posta elettronica, inserire il codice riportato nella mail!");
				AutenticaRegistrazione a = new AutenticaRegistrazione();
				codiceRandom = a.generaCodice();
				area.append("Gestore"+pid+":CODICE RANDOM GENERATO PER UTENTE "+username+"="+codiceRandom+"  "+ora()+"\n");
				a.mandaMailThread(
						email,
						"Codice Registrazione",
						"Salve,\n\n"
								+ "per continuare con la registrazione inserire il seguente codice: "
								+ codiceRandom
								+ "\n\n\n"
								+ "Si prega di non rispondere al seguente messaggio poiche' auto-generato dal server.");
			}else{
				criptaEInvia("SI");
			}
		}else{
			output.writeInt(2);
			output.flush();
		}
	}

	private void eseguiSelect() throws InterruptedException {
		try {
			mutex.acquire();
			rs=d.getStat().executeQuery(esegui);
			mutex.release();
		} catch (SQLException e) {
			area.append("Gestore"+pid+":"+e.getMessage() +" "+ora()+"\n");
			mutex.release();
		}
	}

	private void eseguiInserimento() {
		try {
			mutex.acquire();
			d.getStat().execute(esegui);
			mutex.release();
			inviaOk(1);
		} catch (SQLException e) {
			area.append("Gestore"+pid+":"+e.getMessage() +" "+ora()+"\n");
			mutex.release();
			inviaOk(e.getErrorCode());
		} catch (InterruptedException e) {
			mutex.release();
			e.printStackTrace();
			inviaOk(100);
		}
	}

	private void inviaOk(int i) {
		try {
			output.writeInt(i);
			output.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void criptaEInviaResultSet() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException, ShortBufferException, SQLException{
		int col=rs.getMetaData().getColumnCount();
		while(rs.next()){
			String inv="";
			for(int i=1;i<=col;i++){
				inv+=rs.getString(i)+"#";
			}
			criptaEInvia(inv);
		}criptaEInvia("EOF");
	}
	
	public void chiudi(){
		try {
			client.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void riceviTipo(){
			try {
				tipo=input.readInt();
			} catch (IOException e) {
				connesso=false;
			}
	}

	public void riceviRichiesta() throws InterruptedException{
		try {
			byte[] m=riceviEDecripta();
			esegui=new String(m);
		} catch (IOException e) {
			connesso=false;
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		}
		area.append("Gestore"+pid+": RICHIESTA :'"+esegui+"' "+ora()+"\n");
	}
	
}
